﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Data.SqlClient;
using System.Data;
using Axe.Extensions;
using System.Collections.ObjectModel;
using Axe.Collections;

namespace Axe.Data.Mapping
{
    public class SqlInsert : SqlAction
    {
        static readonly Dictionary<Type, string> m_CachedQueries = new Dictionary<Type, string>();

        public SqlInsert(SqlConnection conn, bool close)
            : base(conn, close)
        {

        }

        public SqlInsert(SqlConnection conn, bool close, SqlTransaction tran, bool commit, bool roolback)
            : base(conn, close,tran,commit,roolback)
        {
            
        }
        
        protected string BuildQuery(Type type)
        {
            MappingDescription instDescription = MappingDescriptionBuilder.GetDescription(type);

            return String.Format("INSERT INTO [{0}] ({1}) VALUES ({2})",
                instDescription.Entity.MappedTableName,
                Utility.Join(instDescription.InsertedFields.Values, ","),
                Utility.Join(instDescription.InsertedFields.Values, ",","@{0}"));
        }

        public bool BatchInsert<T>(params object[] objects)
        {
            bool success = true;
            foreach (object item in objects)
            {
                success &= Insert(item);
            }
            return success;
        }

        public bool BatchInsert<T>(IEnumerable<T> objects) where T:class
        {
            bool success = true;
            foreach (T item in objects)
            {
                success &= Insert<T>(item);
            }
            return success;
        }

        public bool Insert<T>(T classInstance) where T : class
        {
            return Insert(classInstance, classInstance.GetType());
        }

        public bool Insert(object p)
        {
            return Insert(p, p.GetType());
        }

        public bool Insert(object p, Type type)
        {
            if (!type.IsAssignableFrom(p.GetType()))
                throw new ArgumentException(Res_Exceptions.IncomptabileDeclaredAndActualTypes);

            MappingDescription instDescription = MappingDescriptionBuilder.GetDescription(type);

            if (instDescription == null)
                throw new InvalidOperationException("Could not get mapping description");

            lock (m_CachedQueries)
            {
                if (m_CachedQueries.ContainsKey(type) == false)
                {
                    string query = BuildQuery(type);
                    m_CachedQueries.Add(type, query);
                }
            }

            return Template<bool>(
                delegate(SqlCommand cmd)
                {
                    cmd.CommandText = m_CachedQueries[type];

                    // Prepare command
                    foreach (StringPair field in instDescription.InsertedFields)
                    {
                        PropertyDescription pDescr = instDescription.MappedFields[field.Left];
                        object value = pDescr.GetValue(p);
                        cmd.Parameters.AddWithValue("@" + field.Right, value);
                    }
                },
                delegate(SqlCommand cmd)
                {
                    int affectedRows = cmd.ExecuteNonQuery();

                    if (affectedRows == 1)
                    {
                        if (instDescription.HasPrimaryKey)
                        {
                            cmd.CommandText = String.Format("SELECT IDENT_CURRENT('{0}')", instDescription.Entity.MappedTableName);

                            object pkValue = cmd.ExecuteScalar();
                            object convertedPK = Convert.ChangeType(pkValue, instDescription.PrimaryKey.Property.PropertyType);

                            instDescription.PrimaryKey.SetValue(p, convertedPK);
                        }

                        if (CommitTransactionOnSuccess && Transaction != null)
                        {
                            Transaction.Commit();
                        }

                        return true;
                    }

                    return false;
                });
        }
    }
}
